package hash;

import impl.Employee;
import impl.Relative;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

import utils.Arquivos;

public class EncadeamentoExterior {

    private int TAMANHO_HASH;

    public EncadeamentoExterior(int tamanho) {
        TAMANHO_HASH = tamanho;
    }

    /**
     * Cria uma tabela hash vazia de tamanho tam, e salva no arquivo nomeArquivoHash
     * Compartimento que não tem nó deve ter valor igual a -1
     * @param nomeArquivoHash nome do arquivo hash a ser criado
     * @param tam tamanho da tabela hash a ser criada
     * @throws IOException 
     */
    public static void criaHash(String nomeArquivoHash, int tam) throws IOException {
        File f = new File(nomeArquivoHash);
        if (!f.exists()) {
            try {
                RandomAccessFile arqHash = new RandomAccessFile(new File(nomeArquivoHash), "rw");
                for (int i = 0; i < tam; i++) {
                    CompartimentoHash comp = new CompartimentoHash(-1);
                    comp.salva(arqHash);
                }
                Arquivos.fechaArquivo(arqHash);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    public int searchEmployee(int codEmployee) throws Exception {
        int hash = codEmployee % TAMANHO_HASH;

        RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_EMPLOYEE);

        int pos = hash * CompartimentoHash.tamanhoRegistro;
        arqHash.seek(pos);
        CompartimentoHash comp = CompartimentoHash.le(arqHash);

        RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
        int end = comp.prox;
        boolean achou = false;

        while (end != -1 && !achou) {
            arqDados.seek(end * Employee.tamanhoRegistro);
            Employee c = Employee.le(arqDados);
            if (!c.flag) {
                if (c.cod == codEmployee) {
                    achou = true;
                } else {
                    end = c.prox;
                }
            } else if (c.flag) {
                end = c.prox;
            }
        }
        Arquivos.fechaArquivo(arqDados);
        Arquivos.fechaArquivo(arqHash);
        return end;
    }

    public int searchRelative(int codRelative) throws Exception {
        int hash = codRelative % TAMANHO_HASH;

        RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_RELATIVE);

        int pos = hash * CompartimentoHash.tamanhoRegistro;
        arqHash.seek(pos);
        CompartimentoHash comp = CompartimentoHash.le(arqHash);

        RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_RELATIVE);
        int end = comp.prox;
        boolean achou = false;

        while (end != -1 && !achou) {
            arqDados.seek(end * Relative.tamanhoRegistro);
            Relative r = Relative.le(arqDados);
            if (!r.flag) {
                if (r.cod == codRelative) {
                    achou = true;
                } else {
                    end = r.prox;
                }
            } else if (r.flag) {
                end = r.prox;
            }
        }
        Arquivos.fechaArquivo(arqDados);
        Arquivos.fechaArquivo(arqHash);
        return end;
    }

    public int insertEmployee(int code, String name, int age, double salary, int numRegistros) throws Exception {
        boolean inserted = false;
        int resultadoInsercao = Integer.MAX_VALUE;

        int searchResult = searchEmployee(code);

        if (searchResult == -1) {
            int hash = code % TAMANHO_HASH;

            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_EMPLOYEE);
            int pos = hash * CompartimentoHash.tamanhoRegistro;
            arqHash.seek(pos);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);
            int end = comp.prox;

            if (end != -1) {    //compartimento na tabela hash não vazio
                while (!inserted) {
                    arqDados.seek(end * Employee.tamanhoRegistro);
                    Employee c = Employee.le(arqDados);
                    if (!c.flag) {
                        if (c.prox == -1) {
                            arqDados.seek(numRegistros * Employee.tamanhoRegistro);
                            Employee newC = new Employee(code, name, age, salary, -1, Employee.OCUPADO);
                            newC.salva(arqDados);
                            c.prox = numRegistros;
                            arqDados.seek(end * Employee.tamanhoRegistro);
                            c.salva(arqDados);
                            resultadoInsercao = numRegistros;
                            inserted = true;
                        } else {
                            end = c.prox;
                        }
                    } else if (c.flag) {
                        arqDados.seek(end * Employee.tamanhoRegistro);
                        Employee newC = new Employee(code, name, age, salary, c.prox, Employee.OCUPADO);
                        newC.salva(arqDados);
                        resultadoInsercao = end;
                        inserted = true;
                    }
                }
            } else {    //compartimento na tabela hash vazio
                arqDados.seek(numRegistros * Employee.tamanhoRegistro);
                Employee newC = new Employee(code, name, age, salary, -1, Employee.OCUPADO);
                newC.salva(arqDados);
                resultadoInsercao = numRegistros;

                pos = hash * CompartimentoHash.tamanhoRegistro;
                arqHash.seek(pos);
                comp = new CompartimentoHash(resultadoInsercao);
                comp.salva(arqHash);
            }

            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);

            return resultadoInsercao;
        } else {
            return -1;
        }
    }

    public int insertRelative(int code, String name, int age, int employeeCode, int numRegistros) throws Exception {
        boolean inserted = false;
        int resultadoInsercao = Integer.MAX_VALUE;

        int searchResult = searchRelative(code);

        if (searchResult == -1) {
            int hash = code % TAMANHO_HASH;

            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_RELATIVE);
            int pos = hash * CompartimentoHash.tamanhoRegistro;
            arqHash.seek(pos);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_RELATIVE);
            int end = comp.prox;

            if (end != -1) {    //compartimento na tabela hash não vazio
                while (!inserted) {
                    arqDados.seek(end * Relative.tamanhoRegistro);
                    Relative c = Relative.le(arqDados);
                    if (!c.flag) {
                        if (c.prox == -1) {
                            arqDados.seek(numRegistros * Relative.tamanhoRegistro);
                            Relative newR = new Relative(code, name, age, employeeCode, -1, Relative.OCUPADO);
                            newR.salva(arqDados);
                            c.prox = numRegistros;
                            arqDados.seek(end * Relative.tamanhoRegistro);
                            c.salva(arqDados);
                            resultadoInsercao = numRegistros;
                            inserted = true;
                        } else {
                            end = c.prox;
                        }
                    } else if (c.flag) {
                        arqDados.seek(end * Relative.tamanhoRegistro);
                        Relative newR = new Relative(code, name, age, employeeCode, c.prox, Relative.OCUPADO);
                        newR.salva(arqDados);
                        resultadoInsercao = end;
                        inserted = true;
                    }
                }
            } else {    //compartimento na tabela hash vazio
                arqDados.seek(numRegistros * Relative.tamanhoRegistro);
                Relative newR = new Relative(code, name, age, employeeCode, -1, Relative.OCUPADO);
                newR.salva(arqDados);
                resultadoInsercao = numRegistros;

                pos = hash * CompartimentoHash.tamanhoRegistro;
                arqHash.seek(pos);
                comp = new CompartimentoHash(resultadoInsercao);
                comp.salva(arqHash);
            }

            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);

            return resultadoInsercao;
        } else {
            return -1;
        }
    }

    public int deleteEmployee(int code) throws Exception {
        int searchResult = searchEmployee(code);
        boolean deleted = false;

        if (searchResult != -1) {

            int hash = code % TAMANHO_HASH;
            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_EMPLOYEE);
            arqHash.seek(hash * CompartimentoHash.tamanhoRegistro);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);

            int end = comp.prox;

            while (!deleted) {
                arqDados.seek(end * Employee.tamanhoRegistro);
                Employee c = Employee.le(arqDados);
                if (c.cod == code) {
                    c.flag = Employee.LIBERADO;
                    arqDados.seek(end * Employee.tamanhoRegistro);
                    c.salva(arqDados);
                    deleted = true;
                }
                end = c.prox;
            }
            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);
        } else {
            return -1;
        }


        return searchResult;
    }

    public int deleteRelative(int code) throws Exception {
        int searchResult = searchRelative(code);
        boolean deleted = false;

        if (searchResult != -1) {

            int hash = code % TAMANHO_HASH;
            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_RELATIVE);
            arqHash.seek(hash * CompartimentoHash.tamanhoRegistro);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_RELATIVE);

            int end = comp.prox;

            while (!deleted) {
                arqDados.seek(end * Relative.tamanhoRegistro);
                Relative c = Relative.le(arqDados);
                if (c.cod == code) {
                    c.flag = Relative.LIBERADO;
                    arqDados.seek(end * Relative.tamanhoRegistro);
                    c.salva(arqDados);
                    deleted = true;
                }
                end = c.prox;
            }
            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);
        } else {
            return -1;
        }

        return searchResult;
    }

    public int editEmployee(int code, String newName, int newAge, double newSalary) throws Exception {
        int searchResult = searchEmployee(code);
        boolean edited = false;

        if (searchResult != -1) {

            int hash = code % TAMANHO_HASH;
            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_EMPLOYEE);
            arqHash.seek(hash * CompartimentoHash.tamanhoRegistro);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_EMPLOYEE);

            int end = comp.prox;

            while (!edited) {
                arqDados.seek(end * Employee.tamanhoRegistro);
                Employee e = Employee.le(arqDados);
                if (e.cod == code) {
                    if (!newName.isEmpty()) {
                        e.nome = Employee.putName(newName);
                    }
                    if (newAge != -1) {
                        e.age = newAge;
                    }
                    if (newSalary != -1) {
                        e.salary = newSalary;
                    }
                    arqDados.seek(end * Employee.tamanhoRegistro);
                    e.salva(arqDados);
                    edited = true;
                }
                end = e.prox;
            }
            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);
        } else {
            return -1;
        }


        return searchResult;
    }

    public int editRelative(int code, String newName, int newAge, int newEmployeeCode) throws Exception {
        int searchResult = searchRelative(code);
        boolean deleted = false;

        if (searchResult != -1) {

            int hash = code % TAMANHO_HASH;
            RandomAccessFile arqHash = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_HASH_RELATIVE);
            arqHash.seek(hash * CompartimentoHash.tamanhoRegistro);
            CompartimentoHash comp = CompartimentoHash.le(arqHash);

            RandomAccessFile arqDados = Arquivos.abreArquivoSaida(Arquivos.ARQ_TAB_DADOS_RELATIVE);

            int end = comp.prox;

            while (!deleted) {
                arqDados.seek(end * Relative.tamanhoRegistro);
                Relative r = Relative.le(arqDados);
                if (r.cod == code) {
                    if (!newName.isEmpty()) {
                        r.nome = Relative.putName(newName);
                    }
                    if (newAge != -1) {
                        r.age = newAge;
                    }
                    if (newEmployeeCode != -1) {
                        r.codEmployee = newEmployeeCode;
                    }
                    arqDados.seek(end * Relative.tamanhoRegistro);
                    r.salva(arqDados);
                    deleted = true;
                }
                end = r.prox;
            }
            Arquivos.fechaArquivo(arqDados);
            Arquivos.fechaArquivo(arqHash);
        } else {
            return -1;
        }

        return searchResult;
    }
}
